package com.gf.framework.web.swagger;

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeBindings;
import com.fasterxml.classmate.types.ResolvedObjectType;
import com.gf.framework.common.result.R;
import lombok.extern.slf4j.Slf4j;
import springfox.documentation.RequestHandler;
import springfox.documentation.service.ResourceGroup;
import springfox.documentation.spi.service.contexts.ApiSelector;
import springfox.documentation.spi.service.contexts.DocumentationContext;
import springfox.documentation.spi.service.contexts.RequestMappingContext;
import springfox.documentation.spring.web.WebMvcRequestHandler;
import springfox.documentation.spring.web.scanners.ApiListingReferenceScanResult;
import springfox.documentation.spring.web.scanners.ApiListingReferenceScanner;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author gaofei
 * @date 2022/5/22 19:50
 */
@Slf4j
public class SwaggerApiListingReferenceScanner extends ApiListingReferenceScanner {
    private static class InvocationRequestHandler implements InvocationHandler {
        //被代理类的对象
        private final RequestHandler target;

        private InvocationRequestHandler(RequestHandler target){
            this.target = target;
        }

        /**
         * proxy:代表动态代理对象
         * method：代表正在执行的方法
         * args：代表调用目标方法时传入的实参
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            if ("getReturnType".equals(method.getName())) {
                ResolvedType resolvedType = target.getReturnType();
                if (resolvedType.getErasedType() == java.lang.Void.class) {
                    return method.invoke(target, args);
                }
                if (resolvedType.getErasedType() != R.class) {
                    TypeBindings typeBindings = TypeBindings.create(R.class,
                            new ResolvedType[] {
                            target.getReturnType()
                    });

                    return ResolvedObjectType.create(R.class,
                            typeBindings, null,  null);
                }
            }

            return method.invoke(target, args);
        }

    }

    @Override
    public ApiListingReferenceScanResult scan(DocumentationContext context) {
        log.debug("Scanning for api listing references");

        Map<ResourceGroup, List<RequestMappingContext>> resourceGroupRequestMappings
                = new HashMap<>();

        int requestMappingContextId = 0;

        ApiSelector selector = context.getApiSelector();
        RequestHandler[] matchingHandlers = context.getRequestHandlers().stream()
                .filter(selector.getRequestHandlerSelector()).toArray(RequestHandler[]::new);

        List<RequestHandler> requestHandlers = new ArrayList<>(matchingHandlers.length);
        for (RequestHandler requestHandler : matchingHandlers) {
            if (requestHandler instanceof WebMvcRequestHandler) {
                InvocationRequestHandler invocationRequestHandler = new InvocationRequestHandler(requestHandler);
                RequestHandler renterProxy = (RequestHandler) Proxy.newProxyInstance(RequestHandler.class.getClassLoader(),
                        new Class<?>[]{RequestHandler.class}, invocationRequestHandler);
                requestHandlers.add(renterProxy);
            }
            else {
                requestHandlers.add(requestHandler);
            }
        }

        for (RequestHandler handler : requestHandlers) {
            ResourceGroup resourceGroup = new ResourceGroup(
                    handler.groupName(),
                    handler.declaringClass(),
                    0);

            RequestMappingContext requestMappingContext
                    = new RequestMappingContext(
                    String.valueOf(requestMappingContextId),
                    context,
                    handler);

            resourceGroupRequestMappings.putIfAbsent(
                    resourceGroup,
                    new ArrayList<>());
            resourceGroupRequestMappings.get(resourceGroup).add(requestMappingContext);

            ++requestMappingContextId;
        }
        return new ApiListingReferenceScanResult(resourceGroupRequestMappings);
    }
}
